'''
pygeom -- Python Geometric Engine
intersect -- Intersection protocol

Defines the intersection protocol.

Intersection is the act of finding a GE that satisfies the
property of coincidence or containment of two or more GEs.
(For example, a point contained in two lines.)

For the sake of speed and low complexity, the GEs are asked
to intersect each other (the other options are equations,
which are more than difficult to solve without a massive
symbolic library and take time to define and solve but may
be more flexible, or brute force, which is just impractical).

For this reason, the intersection protocol is decently
advanced, using bitfields instead of enumerations. However,
most of the intersection bitfield modes, with notable
exception, correspond to the cls enumerations.
'''

from pygeom import cls

DEFER_ALL=0x00 #The "NULL" value
CLS_POINT=0x01 #GEs with cls.POINT
CLS_LINE=0x02 #GEs with cls.LINE
CLS_PLANE=0x04 #GEs with cls.PLANE
ACCEPT_ALL=0xFF #All qualifiers

NOT_VALID=0x100 #Cannot intersect

ISCT_NAME={DEFER_ALL: 'NULL',
           CLS_POINT: 'Point',
           CLS_LINE: 'Line',
           CLS_PLANE: 'Plane'}

bits=[CLS_POINT, CLS_LINE, CLS_PLANE]

criteria={}

#Bad name? IsctCriterion is a decorator which applies a certain
#verification function (a "criterion")
#to an intersection mode. Using as a decorator automatically
#registers the criterion.
class IsctCriterion(object):
    def __call__(self, mode):
        def criterion_decorator(func, mode=mode):
            global complications
            complications[mode]=func

#Some criteria
@IsctCriterion(DEFER_ALL)
def IC_DEFER_ALL(ge):
    return False

@IsctCriterion(CLS_POINT)
def IC_CLS_POINT(ge):
    return cls.Check(ge.Class, cls.POINT)

@IsctCriterion(CLS_LINE)
def IC_CLS_LINE(ge):
    return cls.Check(ge.Class, cls.LINE)

@IsctCriterion(CLS_PLANE)
def IC_CLS_PLANE(ge):
    return cls.Check(ge.Class, cls.PLANE)

@IsctCriterion(NOT_VALID)
def IC_NOT_VALID(ge):
    raise TypeError('GE %r does not support intersection'%(ge))

#Top level function Intersect
#Obeys all of the IsctModes in place and attempts to intersect
#some GEs.
#All intersect calls are two GEs. It is assumed that three or
#more GE intersects indicate the union of these intersects.
#Oh, and by the way, the syntax allows for you to use
#    Intersect=intersect.Intersect
#in a class definition, and I think GE does that ;)
def Intersect(ge, other, *args):
    ret=_Intersect(ge, other)
    for another in args:
        if ret is None:
            return ret
        ret=_Intersect(ret, another)
    return ret

def _IsctNotFound(ge1, ge2):
    raise RuntimeError('Accepted IsctMode not found on GE %r'%(ge1))

def _Intersect(ge1, ge2):
    #Check if intersection can happen
    if (ge1.IsctModes & NOT_VALID) or (ge2.IsctModes & NOT_VALID):
        raise TypeError('Either %r or %r does not support intersection'%(ge1, ge2))
    #Check compatibility ge1 -> ge2
    cinfo=Compatible(ge1, ge2)
    if cinfo:
        return getattr(ge1, '_Isct'+ISCT_NAME[cinfo.mode], _IsctNotFound)(ge1, ge2)
    #OK, try it the other way
    cinfo=Compatible(ge2, ge1)
    if cinfo:
        return getattr(ge2, '_Isct'+ISCT_NAME[cinfo.mode], _IsctNotFound)(ge2, ge1)
    raise TypeError('GEs %r and %r do not support intersection for each other'%(ge1, ge2))

class Compatible(object):
    def __init__(self, ge1, ge2):
        for mode in bits:
            if (ge1.IsctModes & mode) and criteria[mode](ge2):
                self.mode=mode
                self.isct=True
                break
        else:
            self.mode=None
            self.isct=False
    def __bool__(self):
        return self.isct
